{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：asyncio的原理是什么？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "asyncio 是一个很高级的库，它可以实现高并发。如果你在项目中碰到了性能问题，可能首先想到的是优化算法。但有的时候连算法都无法实现性能的大跃进，那就可能需要考虑并发了，asyncio模块也许就是你最好的选择。\n",
    "\n",
    "答案\n",
    "asyncio 的编程模型就是一个事件循环（event loop），让你可以“在 A 发生时，执行 B”，以此来实现并发。事件循环的思想是连续监视各种资源的状态（例如，网络连接和数据库查询），并在事件发生时（例如，当资源准备好或计时器到期时）触发回调函数的执行。asyncio 主要有一个事件循环作为任务调度器，然后可以用async def定义异步函数作为任务逻辑，通过create_task接口把任务挂到 event loop 上。event loop 的运行过程应该是个不停循环的过程，不停查看等待类别有没有可以执行的任务，如果有的话执行任务，直到碰到await之类的主动让出 event loop 的函数，如此反复。\n",
    "\n",
    "拓展\n",
    "为了让大家更好地理解上面那段事件循环的原理，我们用一段伪代码演示事件循环底层是怎么工作的：\n",
    "\n",
    "while True:\n",
    "    # 启动一个事件循环\n",
    "    for task in tasks:\n",
    "        if task.done(): # 检查资源的状态\n",
    "            task.callback()\n",
    "            tasks.remove(task)\n",
    "    if len(tasks) == 0:\n",
    "        # 如果没有任何任务，终止事件循环\n",
    "        break\n",
    "阅读了上面这段伪代码后，我们发现除了事件循环以外，我们还需要考虑如何创建任务，以及任务结束后的回调函数，等等。asyncio 库也提供了相应的 API 供大家使用。我们已经在上一个问题讲到了协程，asyncio 也是和协程有着密切关系的一个官方异步 IO 库。这个库，底层是事件循环（EventLoop），上层是协程和任务。\n",
    "\n",
    "看看官方文档如何对 asyncio 进行描述的：\n",
    "\n",
    "asyncio 是用来编写并发代码的库，使用 async/await 语法。asyncio被用作多个提供高性能 Python 异步框架的基础，包括网络和网站服务，数据库连接库，分布式任务队列等等。asyncio 往往是构建 IO 密集型和高层级结构化网络代码的最佳选择。\n",
    "\n",
    "asyncio的两个主要版本\n",
    "\n",
    "asyncio 主要有两个版本，一个是 Python3.4 刚引入的 asyncio，它的协程还只是基于生成器的，通过 yield from语法实现，也可以通过装饰器@asyncio.coroutine（已过时）装饰一个函数来定义一个协程。Python3.5 引入了两个新的关键字await和async用来替换@asyncio.coroutine和yield from，这样的语法更加贴合异步 IO，也容易和普通的生成器区别开来。所以我们主要讨论await和async的语法使用。\n",
    "\n",
    "asyncio相关概念\n",
    "\n",
    "asyncio 涉及了两个东西，一个是异步编程，另一个是并发编程。\n",
    "\n",
    "异步编程简单来说就是代码执行的顺序在程序运行前是未知的；\n",
    "并发编程是代码的执行不依赖于其他部分，即便是全都在同一个线程内执行。\n",
    "简单说明一下关于asyncio的一些关键字：\n",
    "\n",
    "事件循环（event_loop）：asyncio程序开启一个无限循环，把一些函数注册到事件循环上，当满足事件发生的时候，调用相应的协程函数；\n",
    "协程（coroutine）：协程对象，指一个使用async关键字定义的函数，它的调用不会立即执行函数，而是会返回一个协程对象。协程对象需要注册到事件循环，由事件循环调用；\n",
    "期物（future）: 代表将来执行或没有执行的任务的结果；\n",
    "任务（task）：一个协程对象就是一个原生可以挂起的函数，任务则是对协程进一步封装，其中包含了任务的各种状态。另外，Task是Future的子类；\n",
    "async/await关键字：python3.5用于定义协程的关键字，async定义一个协程，await用于挂起阻塞的异步调用接口。\n",
    "从一个事例来看看同步编程和异步编程的区别。\n",
    "\n",
    "异步版实现(asyncio_count.py)：\n",
    "\n",
    "import asyncio\n",
    "from datetime import datetime\n",
    "\n",
    "async def count(number): # async def定义一个协程函数，返回协程对象\n",
    "    print(\"One - \", number, datetime.now().strftime(\"%H:%M:%S\"))\n",
    "\n",
    "     # 非阻塞调用，这里可以替换为一些耗时的非阻塞IO操作。这里在等待一个将来的结果，\n",
    "     # 那么与此同时，可以进行其它的一些运算，而count函数就暂停在这里，直到asyncio.sleep返回结果\n",
    "     # await为事件循环提供断点，以便在等待资源时，事件循环可以继续进行并同时管理其他协程。\n",
    "    await asyncio.sleep(1)\n",
    "\n",
    "    print(\"Two - \", number, datetime.now().strftime(\"%H:%M:%S\"))\n",
    "\n",
    "async def main():\n",
    "    # await asyncio.gather(count(1), count(2), count(3)) # asyncio.gather并发运行可等待对象\n",
    "    task1 = asyncio.create_task(count(1)) # create_task定义asyncio任务，协程对象会自动排入日程，并发运行多个协程对象\n",
    "    task2 = asyncio.create_task(count(2))\n",
    "    task3 = asyncio.create_task(count(3))\n",
    "\n",
    "    await task1\n",
    "    await task2\n",
    "    await task3\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    import time\n",
    "    s = time.perf_counter()\n",
    "    asyncio.run(main()) # asyncio.run函数运行传入的协程，它是asyncio程序的主入口点\n",
    "    elapsed = time.perf_counter() - s\n",
    "    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")\n",
    "运行asyncio_count.py脚本输出：\n",
    "\n",
    "One -  1 14:55:17\n",
    "One -  2 14:55:17\n",
    "One -  3 14:55:17\n",
    "Two -  1 14:55:18\n",
    "Two -  2 14:55:18\n",
    "Two -  3 14:55:18\n",
    "asyncio_count.py executed in 1.00 seconds.\n",
    "当每个任务到达等待asyncio.sleep(1)时，count函数都会大喊事件循环并对其进行控制，并说：“我要睡眠1秒钟。让其他有意义的事情同时进行。”\n",
    "\n",
    "同步版实现（sync_count.py）：\n",
    "\n",
    "import time\n",
    "from datetime import datetime\n",
    "\n",
    "def count(number):\n",
    "    print(\"One - \", number, datetime.now().strftime(\"%H:%M:%S\"))\n",
    "    time.sleep(1) # 阻塞调用，无法切换做其它事情\n",
    "    print(\"Two - \", number, datetime.now().strftime(\"%H:%M:%S\"))\n",
    "\n",
    "def main():\n",
    "    for i in range(3):\n",
    "        count(i)\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    s = time.perf_counter()\n",
    "    main()\n",
    "    elapsed = time.perf_counter() - s\n",
    "    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")\n",
    "运行 sync_count.py 脚本输出：\n",
    "\n",
    "One -  0 14:57:22\n",
    "Two -  0 14:57:23\n",
    "One -  1 14:57:23\n",
    "Two -  1 14:57:24\n",
    "One -  2 14:57:24\n",
    "Two -  2 14:57:25\n",
    "sync_count.py executed in 3.01 seconds.\n",
    "这次比上一次异步实现慢了 2 秒，我们发现这一次的输出是按顺序执行的，这就是异步编程与同步编程的差异所在。每次都要等待上一次调用返回结果才能开始新的轮询。\n",
    "\n",
    "asynicio的协程与任务\n",
    "\n",
    "async def定义一个协程函数，它会返回一个协程对象，那么如何去驱动它呢？\n",
    "\n",
    "asynicio 中运行协程的 3 种机制：\n",
    "\n",
    "使用asyncio.run(coroutine)函数运行传入的协程；\n",
    "\n",
    "使用await coroutine去等待一个协程对象，这个语句只能在async def定义的协程函数里使用；\n",
    "\n",
    "Tips: 如果一个对象可以在await语句中使用，那么它就是可等待对象。可等待对象有三种主要类型: 协程，任务Task和期物Future.\n",
    "\n",
    "使用asyncio.create_task(coroutine)去创建Task对象，用于包装协程，把传入的协程排入日程以便并发执行多个协。\n",
    "\n",
    "所以，asynicio 不会像以前我们讲的协程那样，通过调用next和send去驱动协程。\n",
    "\n",
    "asynicio这个库包罗的东西非常多，我们不可能在这么一个问题中就讲清楚所有的内存，但是，大家把上面所讲的东西好好消化一下，自己也可以参考官方文档去动手敲代码，这才是最有效的学习方法。\n",
    "\n",
    "小结\n",
    "这一小章中的问题，无论是 GIL 锁，还是进程，线程，协程，都是和并发、并行编程有着密切关系的。关于并发、并行编程，有许多技巧，这里并没有一一列举，这一节主要是借一些常见的面试题来普及一下相关的基本概念。另外要多看几个主要的库（gevent，concurrent，asyncio等）的官方文档。\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
